// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include <tuple>

#include "pw_function/function.h"
#include "pw_thread/id.h"
#include "pw_thread/thread_core.h"

// clang-format off
// The backend's thread_native header must provide PW_THREAD_JOINING_ENABLED.
#include "pw_thread_backend/thread_native.h"
// clang-format on

namespace pw::thread {

/// The Options contains the parameters needed for a thread to start.
///
/// Options are backend specific and ergo the generic base class cannot be
/// directly instantiated.
///
/// The attributes which can be set through the options are backend specific
/// but may contain things like the thread name, priority, scheduling policy,
/// core/processor affinity, and/or an optional reference to a pre-allocated
/// Context (the collection of memory allocations needed for a thread to run).
///
/// Options shall NOT have an attribute to start threads as detached vs
/// joinable. All `pw::thread::Thread` instances must be explicitly `join()`'d
/// or `detach()`'d through the run-time Thread API.
///
/// Note that if backends set `PW_THREAD_JOINING_ENABLED` to false, backends may
/// use native OS specific APIs to create native detached threads because the
/// `join()` API would be compiled out. However, users must still explicitly
/// invoke `detach()`.
///
/// Options must not contain any memory needed for a thread to run (TCB,
/// stack, etc.). The Options may be deleted or re-used immediately after
/// starting a thread.
class Options {
 protected:
  // We can't use `= default` here, because it allows to create an Options
  // instance in C++17 with `pw::thread::Options{}` syntax.
  constexpr Options() {}
};

/// The class Thread can represent a single thread of execution. Threads allow
/// multiple functions to execute concurrently.
///
/// Threads may begin execution immediately upon construction of the associated
/// thread object (pending any OS scheduling delays), starting at the top-level
/// function provided as a constructor argument. The return value of the
/// top-level function is ignored. The top-level function may communicate its
/// return value by modifying shared variables (which may require
/// synchronization, see pw_sync and std::atomic)
///
/// Thread objects may also be in the state that does not represent any thread
/// (after default construction, move from, detach, or join), and a thread of
/// execution may be not associated with any thread objects (after detach).
///
/// No two Thread objects may represent the same thread of execution; Thread is
/// not CopyConstructible or CopyAssignable, although it is MoveConstructible
/// and MoveAssignable.
class Thread {
 public:
  using native_handle_type = backend::NativeThreadHandle;

  /// Creates a new thread object which does not represent a thread of execution
  /// yet.
  Thread();

  /// Creates a thread from a void-returning function or lambda.
  ///
  /// This function accepts any callable (including lambdas) which returns
  /// ``void``. When using a lambda, the captures must not exceed the inline
  /// size of ``pw::Function`` (usually a single pointer) unless dynamic
  /// allocation is enabled.
  ///
  /// To invoke a member method of a class a static lambda closure can be used
  /// to ensure the dispatching closure is not destructed before the thread is
  /// done executing. For example:
  ///
  /// ```
  /// class Foo {
  ///  public:
  ///   void DoBar() {}
  /// };
  /// Foo foo;
  ///
  /// // Now use the lambda closure as the thread entry, passing the foo's
  /// // this as the argument.
  /// Thread thread(options, [&foo]() { foo.DoBar(); });
  /// thread.detach();
  /// ```
  ///
  /// Alternatively a helper ThreadCore interface can be implemented by an
  /// object so that a lambda closure or function is not needed to dispatch to a
  /// member function without arguments. For example:
  ///
  ///
  /// Postcondition: The thread get EITHER detached or joined.
  ///
  /// NOTE: Options have a default constructor, however default options are not
  /// portable! Default options can only work if threads are dynamically
  /// allocated by default, meaning default options cannot work on backends
  /// which require static thread allocations. In addition on some schedulers
  /// default options may not work for other reasons.
  Thread(const Options& options, Function<void()>&& entry);

  /// Creates a thread from a ``ThreadCore`` subclass.
  ///
  /// The ``ThreadCore`` interface can be implemented by an object so that
  /// a lambda closure or function is not needed to dispatch to a member
  /// function.
  ///
  /// For example:
  ///
  /// @code{.cpp}
  /// class Foo : public ThreadCore {
  ///  private:
  ///   void Run() override {}
  /// };
  /// Foo foo;
  ///
  /// // Now create the thread, using foo directly.
  /// Thread(options, foo).detach();
  /// @endcode
  ///
  /// Postcondition: The thread get EITHER detached or joined.
  ///
  /// NOTE: Options have a default constructor, however default options are not
  /// portable! Default options can only work if threads are dynamically
  /// allocated by default, meaning default options cannot work on backends
  /// which require static thread allocations. In addition on some schedulers
  /// default options may not work for other reasons.
  Thread(const Options& options, ThreadCore& thread_core);

  using ThreadRoutine = void (*)(void* arg);

  /// DEPRECATED: Creates a thread from a void-returning function pointer and
  /// a void pointer argument.
  ///
  /// Postcondition: The thread get EITHER detached or joined.
  ///
  /// NOTE: Options have a default constructor, however default options are not
  /// portable! Default options can only work if threads are dynamically
  /// allocated by default, meaning default options cannot work on backends
  /// which require static thread allocations. In addition on some schedulers
  /// default options may not work for other reasons.
  Thread(const Options& options, ThreadRoutine entry, void* arg = nullptr);

  /// Postcondition: The other thread no longer represents a thread of
  /// execution.
  Thread& operator=(Thread&& other);

  /// Precondition: The thread must have been EITHER detached or joined.
  ~Thread();

  Thread(const Thread&) = delete;
  Thread(Thread&&) = delete;
  Thread& operator=(const Thread&) = delete;

  /// Returns a value of Thread::id identifying the thread associated with
  /// *this. If there is no thread associated, default constructed Thread::id is
  /// returned.
  Id get_id() const;

  /// Checks if the Thread object identifies an active thread of execution which
  /// has not yet been detached. Specifically, returns true if get_id() !=
  /// pw::Thread::id() && detach() has NOT been invoked. So a default
  /// constructed thread is not joinable and neither is one which was detached.
  ///
  /// A thread that has not started or has finished executing code which was
  /// never detached, but has not yet been joined is still considered an active
  /// thread of execution and is therefore joinable.
  bool joinable() const { return get_id() != Id(); }

#if PW_THREAD_JOINING_ENABLED
  /// Blocks the current thread until the thread identified by *this finishes
  /// its execution.
  ///
  /// The completion of the thread identified by *this synchronizes with the
  /// corresponding successful return from join().
  ///
  /// No synchronization is performed on *this itself. Concurrently calling
  /// join() on the same thread object from multiple threads constitutes a data
  /// race that results in undefined behavior.
  ///
  /// Precondition: The thread must have been NEITHER detached nor joined.
  ///
  /// Postcondition: After calling detach *this no longer owns any thread.
  void join();
#else
  template <typename kUnusedType = void>
  void join() {
    static_assert(kJoiningEnabled<kUnusedType>,
                  "The selected pw_thread_THREAD backend does not have join() "
                  "enabled (AKA PW_THREAD_JOINING_ENABLED = 1)");
  }
#endif  // PW_THREAD_JOINING_ENABLED

  /// Separates the thread of execution from the thread object, allowing
  /// execution to continue independently. Any allocated resources will be freed
  /// once the thread exits.
  ///
  /// Precondition: The thread must have been NEITHER detached nor joined.
  ///
  /// Postcondition: After calling detach *this no longer owns any thread.
  void detach();

  /// Exchanges the underlying handles of two thread objects.
  void swap(Thread& other);

  native_handle_type native_handle();

 private:
  template <typename...>
  static constexpr std::bool_constant<PW_THREAD_JOINING_ENABLED>
      kJoiningEnabled = {};

  // Note that just like std::thread, this is effectively just a pointer or
  // reference to the native thread -- this does not contain any memory needed
  // for the thread to execute.
  //
  // This may contain more than the native thread handle to enable functionality
  // which is not always available such as joining, which may require a
  // reference to a binary semaphore, or passing arguments to the thread's
  // function.
  backend::NativeThread native_type_;
};

}  // namespace pw::thread

#include "pw_thread_backend/thread_inline.h"
